<?php
/**
 * PHP Client for Tokyo Tyrant
 *
 * Pure PHP implementation of the binary Tokyo Tyrant 1.1.11 protocol
 * Tokyo Cabinet <http://tokyocabinet.sourceforge.net/> is a 
 * "super hyper ultra database manager" written and maintained by Mikio Hirabayashi 
 * and released under the LGPL.
 *
 * Tokyo Tyrant is the de facto database server for Tokyo Cabinet written 
 * and maintained by the same author. It supports a REST HTTP protocol, memcached, and 
 * its own simple binary protocol. This library implements the full binary protocol 
 * for the Tokyo Tyrant 1.1.11 in pure Python as defined here::
 *
 * http://tokyocabinet.sourceforge.net/tyrantdoc/
 *
 * PHP version 5
 *
 * @category  PHP
 * @package   Tokyo_Tyrant
 * @author    Tamas Kalman <ktamas77@gmail.com>
 * @license   BSD
 */

define('DEFAULT_PORT', 1978);
define('MAGIC', 0xc8);

define('RDBMONOULOG', 1 << 0);
define('RDBXOLCKREC', 1 << 0);
define('RDBXOLCKGLB', 1 << 1);

/**
 * Tyrant Protocol Constants
 */
$TT_keys = Array (
	"put" => 0x10,
	"putkeep" => 0x11,
	"putcat" => 0x12,
	"putshl" => 0x13,
	"putnr" => 0x18,
	"out" => 0x20,
	"get" => 0x30,
	"mget" => 0x31,
	"vsiz" => 0x38,
	"iterinit" => 0x50,
	"iternext" => 0x51,
	"fwmkeys" => 0x58,
	"addint" => 0x60,
	"adddouble" => 0x61,
	"ext" => 0x68,
	"sync" => 0x70,
	"vanish" => 0x71,
	"copy" => 0x72,
	"restore" => 0x73,
	"setmst" => 0x78,
	"rnum" => 0x80,
	"size" => 0x81,
	"stat" => 0x88,
	"misc" => 0x90
);

function _t0($code)
{
	return chr(MAGIC) + chr($code);
}

function _t1($code, $key)
{
	$p = pack("ccN", MAGIC, $code, strlen($key)).$key;
	return $p;
}

function _t1FN($code, $func, $opts, $args)
{
	// --- todo ---
}

function _t1R($code, $key, $msec)
{
	// --- todo ---
}

function _t1M($code, $key, $count)
{
	// --- todo ---
}

function _tN($code, $klst)
{
	// --- todo ---
}

function _t2($code, $key, $value)
{
	$p = pack("ccNN", MAGIC, (int)$code, strlen($key), strlen($value)).$key.$value;
	return $p;
}

function _t2w($code, $key, $value, $width)
{
	// --- todo ---
}

function _t3F($code, $func, $opts, $key, $value)
{
	// --- todo ---
}

function socksend($sock, $lst)
{
	// --- todo ---
}

function sockrecv($sock, $bytes)
{
	$d = "";
	while (strlen($d) < $bytes) {
		$len = min(8192, $bytes - strlen($d));
		// echo "Reading $len bytes...\n";
		$d .= socket_read($sock, $len);
	}
	return $d;
}

function socksuccess($sock)
{
	$fail_code = ord(sockrecv($sock, 1));
	if ($fail_code !== 0) {
		echo "Socket error\n";
	}
}

function socklen($sock)
{
	$v = unpack("N", sockrecv($sock, 4));
	return $v[1];
}

function socklong($sock)
{
	// --- todo ---
}

function sockstr($sock)
{
	return sockrecv($sock, socklen($sock));
}

function sockstrpair($sock)
{
	$klen = socklen($sock);
	$vlen = socklen($sock);
	$k = sockrecv($sock, $klen);
	$v = sockrecv($sock, $vlen);
	return Array ($k, $v);
}

function str2hex($string)
{
    $hex = '';
    for ($i=0; $i < strlen($string); $i++) {
        $hex .= dechex(ord($string[$i]));
    }
    return $hex;
}

class PHPTyrant {
}

class Tyrant {
	
	var $socket;
	
	public function open ($host = "127.0.0.1", $port = DEFAULT_PORT)
	{
		$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		socket_connect($this->socket, $host, $port);
	}
	
	function __construct($socket = null)
	{
		global $TT_keys;
		
		$this->C = $TT_keys;
		$this->socket = $socket;
	}
	
	public function close()
	{
		socket_close($this->socket);
	}          
	
	/**
	 * Unconditionally set key to value
	 */
	public function put($key, $value)
	{
		$data = _t2($this->C["put"], $key, $value);
		// var_dump(str2hex($data));
		socket_write($this->socket, $data);
		socksuccess($this->socket);
	}
	
	/**
	 * Set ket to value if key does not already exist
	 */
	public function putkeep($key, $value)
	{
		socket_write($this->socket, _t2($this->C["putkeep"], $key, $value));
		socksuccess($this->socket);
	}
	
	/**
	 * Append value to the existing value for key, or set key to
	 * value if it does not already exist
	 */
	public function putcat($key, $value)
	{
		socket_write($this->socket, _t2($this->C["putcat"], $key, $value));
		socksuccess($this->socket);
	}
	
	public function putshl($key, $value, $width)
	{
		socket_write($this->socket, _t2W($this->C["putshl"], $key, $value, $width));
		socksuccess($this->socket);
	}
	
	/**
	 * Set key to value without waiting for a server response
	 */
	public function putnr($key, $value)
	{
		socket_write($this->socket, _t2($this->C["putnr"], $key, $value));
	}
	
	/**
	 * Remove key from server
	 */
	public function out($key)
	{
		socket_write($this->socket, _t1($this->C["out"], $key));
		socksuccess($this->socket);
	}
	
	/**
	 * Get the value of a key from the server
	 */
	public function get($key)
	{
		socket_write($this->socket, _t1($this->C["get"], $key));
		socksuccess($this->socket);
		return sockstr($this->socket);
	}
	
	private function _mget($klst)
	{
		socket_write($this->socket, _tN($this->C["mget"], $klst));
		socksuccess($this->socket);
		$numrecs = socklen($this->socket);
		// --- todo ---		
	}
	
	/**
	 * Get key, value pairs from the server for the given list of keys
	 */
	public function mget($klst)
	{
		return $this->_mget($klst);
	}
	
	/**
	 * Get the size of a value for a key
	 */
	public function vsiz($key)
	{
		socket_write($this->socket, _t1($this->C["vsiz"], $key));
		socksuccess($this->socket);
		return socklen($this->socket);
	}
	
	/**
	 * Begin iteration over all keys of the database
	 */
	public function iterinit()
	{
		socket_write($this->socket, _t0($this->C["iterinit"]));
		socksuccess($this->socket);
	}
	
	/**
	 * Get the next key after iterinit
	 */
	public function iternext()
	{
		socket_write($this->socket, _t0($this->C["iternext"]));
		socksuccess($this->socket);
		return sockstr($this->socket);
	}
	
}

?>